#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <tool/ysortedList.h>
#include <mkindex/wordPages.h>
#include <mkindex/refChar.h>

/* START: propertiesFile configuration will override these values */
/* Following is use by refChar_fwrite(..)
e.g. ref['%CHAR%']='%WORD_AND_PAGES_LINK%'; */
#define GEN_TOTAL_RELATIONS     "// total relations: %lu\n"
#define GEN_TOTAL_WORDS         "// words: %lu\n"
#define GEN_REFCHAR_START       "ref['%c']='"
#define GEN_REFCHAR_END         "';\n"
#define GEN_WORDLINK            "%s="
#define GEN_WORDLINK_1ST_PAGE   "%s"
#define GEN_WORDLINK_OTHER_PAGE ",%s"
#define GEN_WORDLINK_SEPARATOR  ";"
/* END: propertiesFile configuration will override these values */

/** Reference character data struct, declare beforehand for use */
typedef struct rcData rcData;

/** Reference character data struct */
struct rcData {
  char aChar;
  sortedList list;
};

/** Reference character data struct */
struct refCharData {
  /** Type <code>wordPagesFn</code> */
  wordPagesFn wpfn;
  /** Type <code>strFn</code> */
  strFn strFn;
  /** reference char (<code>rcData</code>) list */
  sortedList rcList;
  /** Empty <code>rcData</code> for comparison use. */
  rcData *cmpRcData;
  /** Empty <code>wordPages</code> for comparison use. */
  wordPages cmpWordPages;
  /** Contain the value of sizeof(struct rcData) */
  unsigned char sizeof_rcData;
  /** Contain the value of sizeof(char **) */
  unsigned char sizeof_charP;
  /** counter of total words, counter of total word & page relations */
  unsigned long cntTotalWords, cntTotalRelations;
  /** If type <code>strFn</code> was pass in and 
    not created by library's function. */
  boolean setStrFn;
  /** Default <code>ignoreNumbers</code> is set to <code>true</code>. 
  If set to <code>true</code> <code>refChar</code> will ignore words 
  that are made up all of digits. Otherwise set to <code>false</code> will 
  include words that are made all of digits.
  */
  boolean ignoreNumbers;
  /** Default <code>lesserFirst</code> is set to <code>true</code>. 
  If set to <code>true</code> <code>refChar</code> 
  will return list of words whose ASCII value is lesser at the front. 
  Otherwise set to <code>false</code> <code>refChar</code> 
  will return list of words whose ASCII value is greater at the front. 
  */
  boolean lesserFirst;
  /** Type propertiesFile to use. */
  propertiesFile conf;
  /** propertiesFile generated strings setting. */
  char *genTotalRelations,
    *genTotalWords,
    *genRefCharStart,
    *genRefCharEnd,
    *genWordLink,
    *genWordLink1stPage,
    *genWordLinkOtherPage,
    *genWordLinkSeparator;
  boolean bSetTotalRelations,
    bSetTotalWords,
    bSetRefCharStart,
    bSetRefCharEnd,
    bSetWordLink,
    bSetWordLink1stPage,
    bSetWordLinkOtherPage,
    bSetWordLinkSeparator;
};

/*======= declare internal function */
boolean refChar_setStrFn(refChar ref, strFn strFn);
boolean refChar_free(refChar *refP);
/* specific operation */
boolean refChar_add(refChar ref, char **wordArray, 
    const unsigned char wordArrayLen, 
    const char *pageNum, const boolean forceAdd);
boolean refChar_addWord(refChar ref, char *aWord,
    const char *pageNum, const boolean forceAdd);
boolean refChar_fwrite(const refChar ref, FILE *stream);
boolean refChar_setIgnoreNumbers(refChar ref, 
    const boolean ignoreNumbers);
boolean refChar_setLesserFirst(refChar ref, const boolean lesserFirst);
boolean refChar_setPropertiesFile(refChar ref, propertiesFile conf);
int refChar_size(const refChar ref);
/*--- function ONLY for internal use ---*/
boolean refChar_freeGenStr(refChar ref);
rcData *refChar_createRcData(refChar ref, 
    char aChar, wordPages aWordPages);
boolean refChar_freeRcData(void **rcDataP);
g_reply refChar_compareRcDatas(const void *aRcData1, 
    const void *aRcData2);
boolean refChar_rcListAdd(refChar ref, const char *word, 
    const unsigned int wordLen, const char *aPage);
boolean refChar_rcListDoEqualAction(sortedList rcList, 
    void *anItem, void *aControlObj);
boolean refChar_rcListDoAddPrepareAction(void *aControlObj);
boolean refChar_wordListDoEqualAction(sortedList list, 
    void *anItem, void *aControlObj);
boolean refChar_wordListDoAddPrepareAction(void *aControlObj);

/*======= directly exposed function */
/** Create a reference char struct for use which has the 
letters A to Z, a to z and '_'.

Default <code>ignoreNumbers</code> is set to <code>true</code>. 
If set to <code>true</code> <code>refChar</code> will ignore words 
that are made up all of digits. Otherwise set to <code>false</code> will 
include words that are made all of digits.

Default <code>lesserFirst</code> is set to <code>true</code>. 
If set to <code>true</code> <code>refChar</code> 
will return list of words whose ASCII value is lesser at the front. 
Otherwise set to <code>false</code> <code>refChar</code> 
will return list of words whose ASCII value is greater at the front. 
@return a working newly created reference char struct 
  or <code>NULL</code> if failure.
*/
refChar refChar_create(void) {
  return refChar_createWith(NULL);
}

/** Create a reference char struct for use which has the 
letters A to Z, a to z and '_'.

Default <code>ignoreNumbers</code> is set to <code>true</code>. 
If set to <code>true</code> <code>refChar</code> will ignore words 
that are made up all of digits. Otherwise set to <code>false</code> will 
include words that are made all of digits.

Default <code>lesserFirst</code> is set to <code>true</code>. 
If set to <code>true</code> <code>refChar</code> 
will return list of words whose ASCII value is lesser at the front. 
Otherwise set to <code>false</code> <code>refChar</code> 
will return list of words whose ASCII value is greater at the front. 
@param conf the propertiesFile to use.
@return a working newly created reference char struct 
  or <code>NULL</code> if failure.
*/
refChar refChar_createWith(propertiesFile conf) {
  refChar ref;

  FN_MALLOC_ERRMSG(ref, sizeof(struct refChar), 
      "Error: refChar_createWith(..) malloc(ref)")
  if (ref == NULL) {return NULL;}
  FN_MALLOC_ERRMSG(ref->d, sizeof(struct refCharData), 
      "Error: refChar_createWith(..) malloc(w)")
  if (ref->d == NULL) {refChar_free(&ref); return NULL;}
  /*--- data ---*/
  ref->d->wpfn = wordPages_createFn();
  if (ref->d->wpfn == NULL) {
    perror("Error: refChar_createWith(..) ref->d->wpfn == NULL");
    refChar_free(&ref);
    return NULL;
  }
  /* propertiesFile */
  if (refChar_setPropertiesFile(ref, conf) == false) {
    perror("Error: refChar_createWith(..) refChar_setPropertiesFile(..) == false");
    refChar_free(&ref);
    return NULL;
  }
  ref->d->strFn = NULL;
  ref->d->setStrFn = false;
  /* rcData list, a sorted list of reference char. 
    Will create an empty linked list here, 
    so other area can just add without the worry of test */
  ref->d->rcList = ysortedList_create(refChar_freeRcData, refChar_compareRcDatas);
  if (ref->d->rcList == NULL) {
    perror("Error: refChar_createWith(..) ref->d->rcList == NULL");
    refChar_free(&ref);
    return NULL;
  }
  ref->d->rcList->setFnDoEqualAction(ref->d->rcList, 
      refChar_rcListDoEqualAction);
  ref->d->rcList->setFnDoAddPrepareAction(ref->d->rcList, 
      refChar_rcListDoAddPrepareAction);
  /* rcData made specially for comparison,
    it's an empty rcData, NULL as key & an empty word list */
  ref->d->cmpRcData = refChar_createRcData(ref, '\0', NULL);
  if (ref->d->cmpRcData == NULL) {
    perror("Error: refChar_createWith(..) refChar_createRcData(..) == NULL");
    refChar_free(&ref);
    return NULL;
  }
  /* wordPages made specially for comparison,
    it's an empty wordPages, NULL as key & an empty page list */
  ref->d->cmpWordPages = ref->d->wpfn->createWith(ref->d->wpfn, NULL, 0, NULL);
  if (ref->d->cmpWordPages == NULL) {
    perror("Error: refChar_createWith(..) ..->wpfn->createWith(..) == NULL");
    refChar_free(&ref);
    return NULL;
  }
  ref->d->sizeof_rcData = sizeof(struct rcData);
  ref->d->sizeof_charP = sizeof(char *);
  ref->d->cntTotalWords = 0;
  ref->d->cntTotalRelations = 0;
  ref->d->ignoreNumbers = true;
  ref->d->lesserFirst = true;

  /*--- init function pointers ---*/
  ref->setStrFn = refChar_setStrFn;
  ref->free = refChar_free;
  /* specific operation */
  ref->add = refChar_add;
  ref->addWord = refChar_addWord;
  ref->fwrite = refChar_fwrite;
  ref->setIgnoreNumbers = refChar_setIgnoreNumbers;
  ref->setLesserFirst = refChar_setLesserFirst;
  ref->setPropertiesFile = refChar_setPropertiesFile;
  ref->size = refChar_size;
  return ref;
}

/*======= exposed through function pointers */
/** Set <code>refChar</code>'s <code>strFn</code>.
If <code>strFn</code> is already created, 
this action will be ignored and <code>false</code> will be returned.
<code>refChar</code> will NOT free the memory of 
<code>strFn</code> that is created externally, 
you MUST free this yourself.
@param ref the <code>refChar</code> to use.
@param strFn pass in the <code>strFn</code> to use.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_setStrFn(refChar ref, strFn strFn) {
  if (ref == NULL || ref->d == NULL || strFn == NULL) {
    return false;
  }
  ref->d->strFn = strFn;
  ref->d->setStrFn = true;
  /* lets use created strFn */
  ref->d->wpfn->setStrFn(ref->d->wpfn, ref->d->strFn);
  return true;
}

/** Free a reference char struct, free up the memory.
@param refP pass in the address of 
  reference char struct pointer to be free.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_free(refChar *refP) {
  /* NOT critical if refChar is NULL, assume all's fine */
  if (*refP == NULL) {return true;}
  if ((*refP)->d != NULL) {
    /* free rcList */
    if ((*refP)->d->rcList != NULL
        && (*refP)->d->rcList->free(&(*refP)->d->rcList) == false) {
      /* possible memory leak */
      perror("Error: refChar_free(..) ..(*refP)->d->rcList->free(&(*refP)->d->rcList) == false");
    }
    /* free rcData made specially for comparison */
    if ((*refP)->d->cmpRcData != NULL
        && refChar_freeRcData((void *) &(*refP)->d->cmpRcData) == false) {
      /* possible memory leak */
      perror("Error: refChar_free(..) ..refChar_freeRcData(..) == false");
    }
    /* free wpfn */
    if ((*refP)->d->wpfn != NULL
      && (*refP)->d->wpfn->freeFn(&(*refP)->d->wpfn) == false) {
      /* possible memory leak */
      perror("Error: refChar_free(..) (*refP)->d->wpfn->freeFn(&(*refP)->d->wpfn) == false");
    }
    /* free strFn ONLY if it is created within library's function */
    if ((*refP)->d->setStrFn == false
        && (*refP)->d->strFn != NULL
        && (*refP)->d->strFn->freeFn(&(*refP)->d->strFn) == false) {
      /* possible memory leak */
      perror("Error: refChar_free(..) ..(*refP)->d->strFn->freeFn(&(*refP)->d->strFn) == false");
    }
    /* free propertiesFile write/generated strings */
    refChar_freeGenStr(*refP);
    /* free refCharData */
    free((*refP)->d);
    (*refP)->d = NULL;
  }
  /* free refChar */
  free(*refP);
  *refP = NULL;
  return true;
}

/** Add a word list with their page number into <code>refChar</code>.
@param ref pass in the reference char struct.
@param wordArray the word array that contains the word list.
@param wordArrayLen the length of the <code>wordArray</code>
@param pageNum the page number to add.
@param forceAdd forcing the words in <code>wordArray</code> to be added. 
  Against any setting such as <code>ignoreNumbers</code> 
  (overide value using <code>setIgnoreNumbers(refChar, boolean)</code>)
  or <code>lesserFirst</code> 
  (overide value using <code>setLesserFirst(refChar, boolean)</code>)
  However empty string or word is NOT added, 
  even if <code>forceAdd</code> is set to <code>true</code>.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_add(refChar ref, char **wordArray, 
    const unsigned char wordArrayLen, 
    const char *pageNum, const boolean forceAdd) {
  /* Slower if test for existen of function pointers i.e.
      || ref->d->strFn->isNumbersWithLen == NULL 
    Assume type strFn does it's job properly can skip function pointer test.
  */
  if (ref == NULL || ref->d == NULL || wordArray == NULL 
      || wordArrayLen == 0) {
    return false;
  }
  unsigned int i, wordLen;
  for (i = 0; i < wordArrayLen; i++) {
    wordLen = strlen(wordArray[i]);
    /* ignore empty string */
    if (wordLen == 0) {continue;}
    if (ref->d->strFn == NULL) {
      /**=== START: refChar_initStrFn(..) 
      refChar_initStrFn(ref);
      ===**/
      /** Initialize <code>refChar</code>'s <code>strFn</code>.
      Assumtion: previous code check all pass in parameter to be valid!
      This function is for internal use ONLY, do NOT expose this.
      @param ref the <code>refChar</code> to use.
      @return <code>true</code> if successfully otherwise <code>false</code>.
      boolean refChar_initStrFn(refChar ref) {
        //.. code has been merged into calling function ..//
        return true;
      }
      */
      ref->d->strFn = ystr_createFn();
      if (ref->d->strFn == NULL) {
        /* unrecoverable error, can't continue */
        perror("Error: refChar_add(refChar, char **) ystr_createFn() == NULL");
        return false;
      }
      /**=== END: refChar_initStrFn(..) ===**/
    }
    /* ignore all digits? */
    if (forceAdd != true
        && ref->d->ignoreNumbers == true 
        && ref->d->strFn->isNumbersWithLen(wordArray[i], wordLen)) {
        continue;
    }
    if (refChar_rcListAdd(ref, wordArray[i], wordLen, pageNum) == false) {
      /* skip a word & its page */
      perror("Error: refChar_add(refChar, char **) refChar_rcListAdd(..) == false");
    }
  } /* for */
  return true;
}

/** Add a word with its' page number into <code>refChar</code>.
@param ref pass in the reference char struct.
@param aWord the word.
@param pageNum the page number to add.
@param forceAdd forcing the words in <code>wordArray</code> to be added. 
  Against any setting such as <code>ignoreNumbers</code> 
  (overide value using <code>setIgnoreNumbers(refChar, boolean)</code>)
  or <code>lesserFirst</code> 
  (overide value using <code>setLesserFirst(refChar, boolean)</code>)
  However empty string or word is NOT added, 
  even if <code>forceAdd</code> is set to <code>true</code>.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_addWord(refChar ref, char *aWord,
    const char *pageNum, const boolean forceAdd) {
  if (ref == NULL || ref->d == NULL || aWord == NULL 
      || pageNum == NULL) {
    return false;
  }
  char **wordArray;
  FN_MALLOC_ERRMSG_EXIT(wordArray, ref->d->sizeof_charP, 
      "Error: refChar_addWord(..) malloc(wordArray)")
  wordArray[0] = aWord;
  boolean isOK = refChar_add(ref, wordArray, 1, pageNum, forceAdd);
  free(wordArray);
  return isOK;
}

/** Write the current string representation of <code>refChar</code>
according to the requirement to the stream.
@param ref pass in the reference char struct.
@param stream the output stream to write to.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_fwrite(const refChar ref, FILE *stream) {
  if (ref == NULL || ref->d == NULL || stream == NULL) {return false;}
  boolean hasMore;
  wordPages wp;
  char *word, **page;
  unsigned int pageLen, i;
  /* function pointers: */
  boolean (*goStart)(sortedList);
  boolean (*goMove)(sortedList);
  /* determine writing order */
  if (ref->d->lesserFirst == true) {
    /* order by lesser first, 
      start from 1st item in the list */
    goStart = ref->d->rcList->goFirst;
    goMove = ref->d->rcList->goNext;
  } else {
    /* order by greater first 
      start from last item in the list */
    goStart = ref->d->rcList->goLast;
    goMove = ref->d->rcList->goBack;
  }
  /*--- start writing ---*/
  /* number of words */
  fprintf(stream, ref->d->genTotalWords, ref->d->cntTotalWords);
  if (ref->d->rcList->goFirst(ref->d->rcList) == true) {
    rcData *curRcData;
    do {
      curRcData = (rcData *) ref->d->rcList->getItem(ref->d->rcList);
      /*--- START: writing of individual reference char */
      fprintf(stream, ref->d->genRefCharStart, curRcData->aChar);
      if (goStart(curRcData->list) == true) {
        /* write list of words */
        do {
          /* obtain item from sorted list, 
            WARNING: do NOT free item's memory, sorted list will do this. */
          wp = (wordPages) curRcData->list->getItem(curRcData->list);
          /* obtain the word from within our wordPages 
            NOT a cloned copy for speed */
          word = ref->d->wpfn->getWord(wp);
          if (word == NULL) {
            /* unrecoverable error, can't continue */
            perror("Error: refChar_fwrite(refChar, FILE *) ref->d->wpfn->getWord(..) == NULL");
            return false;
          }
          /* write the word */
          fprintf(stream, ref->d->genWordLink, word);
          /* write the pages from within our wordPages 
            NOT a cloned copy for speed */
          page = ref->d->wpfn->getPages(wp, &pageLen);
          if (page == NULL || pageLen == 0) {
            /* unrecoverable error, can't continue */
            perror("Error: refChar_fwrite(refChar, FILE *) page == NULL || pageLen == 0");
            return false;
          }
          fprintf(stream, ref->d->genWordLink1stPage, page[0]);
          for (i = 1; i < pageLen; i++) {
            fprintf(stream, ref->d->genWordLinkOtherPage, page[i]);
          }
          /* any more word to write? */
          hasMore = goMove(curRcData->list);
          if (hasMore == true) {
            fprintf(stream, ref->d->genWordLinkSeparator);
          }
        } while (hasMore == true);
      }
      /*--- END: writing of individual reference char */
      fprintf(stream, ref->d->genRefCharEnd);
    } while (ref->d->rcList->goNext(ref->d->rcList) == true);
  } /* if */
  /* number of total relation of words & their pages */
  fprintf(stream, ref->d->genTotalRelations, ref->d->cntTotalRelations);
  return true;
}

/** Change <code>ignoreNumbers</code> value.
Default <code>ignoreNumbers</code> is set to <code>true</code>. 
If set to <code>true</code> <code>refChar</code> will ignore words 
that are made up all of digits. Otherwise set to <code>false</code> will 
include words that are made all of digits.
@param ref pass in the reference char struct.
@param ignoreNumbers to include numbers set this to <code>false</code> 
  otherwise set to <code>true</code> to ignore the numbers.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_setIgnoreNumbers(refChar ref, 
    const boolean ignoreNumbers) {
  if (ref == NULL || ref->d == NULL) {return false;}
  ref->d->ignoreNumbers = ignoreNumbers;
  return true;
}

/** Change <code>lesserFirst</code> value.
Default <code>lesserFirst</code> is set to <code>true</code>. 
If set to <code>true</code> <code>refChar</code> 
will return list of words whose ASCII value is lesser at the front. 
Otherwise set to <code>false</code> <code>refChar</code> 
will return list of words whose ASCII value is greater at the front. 
@param ref pass in the reference char struct.
@param lesserFirst to sort list by greater first set this to <code>false</code> 
  otherwise set to <code>true</code> to place lesser first.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_setLesserFirst(refChar ref, const boolean lesserFirst) {
  if (ref == NULL || ref->d == NULL) {return false;}
  ref->d->lesserFirst = lesserFirst;
  return true;
}

/** Set the propertiesFile to use.
@param ref pass in the reference char struct.
@param conf the propertiesFile to use, 
  pass in <code>NULL</code> to use none.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_setPropertiesFile(refChar ref, propertiesFile conf) {
  if (ref == NULL || ref->d == NULL) {return false;}
  /* free any previous malloc generate strings */
  refChar_freeGenStr(ref);
  if (conf == NULL) {
    ref->d->genTotalRelations = GEN_TOTAL_RELATIONS;
    ref->d->genTotalWords = GEN_TOTAL_WORDS;
    ref->d->genRefCharStart = GEN_REFCHAR_START;
    ref->d->genRefCharEnd = GEN_REFCHAR_END;
    ref->d->genWordLink = GEN_WORDLINK;
    ref->d->genWordLink1stPage = GEN_WORDLINK_1ST_PAGE;
    ref->d->genWordLinkOtherPage = GEN_WORDLINK_OTHER_PAGE;
    ref->d->genWordLinkSeparator = GEN_WORDLINK_SEPARATOR;
    return true;
  }
  /* our new propertiesFile */
  ref->d->conf = conf;
  /* prepare write/generated strings */
  ref->d->genTotalRelations = 
    ref->d->conf->getStringES(ref->d->conf, "generate.ref.totalRelations");
  if (ref->d->genTotalRelations == NULL) {
    ref->d->genTotalRelations = GEN_TOTAL_RELATIONS;
    ref->d->bSetTotalRelations = false;
  } else {
    ref->d->bSetTotalRelations = true;
  }
  ref->d->genTotalWords = 
    ref->d->conf->getStringES(ref->d->conf, "generate.ref.totalWords");
  if (ref->d->genTotalWords == NULL) {
    ref->d->genTotalWords = GEN_TOTAL_WORDS;
    ref->d->bSetTotalWords = false;
  } else {
    ref->d->bSetTotalWords = true;
  }
  ref->d->genRefCharStart = 
    ref->d->conf->getStringES(ref->d->conf, "generate.ref.refChar.start");
  if (ref->d->genRefCharStart == NULL) {
    ref->d->genRefCharStart = GEN_REFCHAR_START;
    ref->d->bSetRefCharStart = false;
  } else {
    ref->d->bSetRefCharStart = true;
  }
  ref->d->genRefCharEnd = 
    ref->d->conf->getStringES(ref->d->conf, "generate.ref.refChar.end");
  if (ref->d->genRefCharEnd == NULL) {
    ref->d->genRefCharEnd = GEN_REFCHAR_END;
    ref->d->bSetRefCharEnd = false;
  } else {
    ref->d->bSetRefCharEnd = true;
  }
  ref->d->genWordLink = 
    ref->d->conf->getStringES(ref->d->conf, "generate.ref.wordLink");
  if (ref->d->genWordLink == NULL) {
    ref->d->genWordLink = GEN_WORDLINK;
    ref->d->bSetWordLink = false;
  } else {
    ref->d->bSetWordLink = true;
  }
  ref->d->genWordLink1stPage = 
    ref->d->conf->getStringES(ref->d->conf, "generate.ref.wordLink.1stPage");
  if (ref->d->genWordLink1stPage == NULL) {
    ref->d->genWordLink1stPage = GEN_WORDLINK_1ST_PAGE;
    ref->d->bSetWordLink1stPage = false;
  } else {
    ref->d->bSetWordLink1stPage = true;
  }
  ref->d->genWordLinkOtherPage = 
    ref->d->conf->getStringES(ref->d->conf, "generate.ref.wordLink.otherPage");
  if (ref->d->genWordLinkOtherPage == NULL) {
    ref->d->genWordLinkOtherPage = GEN_WORDLINK_OTHER_PAGE;
    ref->d->bSetWordLinkOtherPage = false;
  } else {
    ref->d->bSetWordLinkOtherPage = true;
  }
  ref->d->genWordLinkSeparator = 
    ref->d->conf->getStringES(ref->d->conf, "generate.ref.wordLink.separator");
  if (ref->d->genWordLinkSeparator == NULL) {
    ref->d->genWordLinkSeparator = GEN_WORDLINK_SEPARATOR;
    ref->d->bSetWordLinkSeparator = false;
  } else {
    ref->d->bSetWordLinkSeparator = true;
  }
  return true;
}

/** Returns the number of elements in this <code>refChar</code>.
@param ref pass in the reference char struct.
@return Returns the number of elements in this 
  <code>refChar</code> or -1 if failure.
*/
int refChar_size(const refChar ref) {
  if (ref == NULL || ref->d == NULL) {return -1;}
  return ref->d->rcList->size(ref->d->rcList);
}

/*--- function ONLY for internal use ---*/
/** Free propertiesFile write/generated strings within 
the reference char struct, free up the memory.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param ref pass in the reference char struct pointer to be free.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_freeGenStr(refChar ref) {
  /* free propertiesFile write/generated strings */
  if (ref->d->conf != NULL) {
    if (ref->d->bSetTotalRelations == true) {
      free(ref->d->genTotalRelations);
    }
    if (ref->d->bSetTotalWords == true) {
      free(ref->d->genTotalWords);
    }
    if (ref->d->bSetRefCharStart == true) {
      free(ref->d->genRefCharStart);
    }
    if (ref->d->bSetRefCharEnd == true) {
      free(ref->d->genRefCharEnd);
    }
    if (ref->d->bSetWordLink == true) {
      free(ref->d->genWordLink);
    }
    if (ref->d->bSetWordLink1stPage == true) {
      free(ref->d->genWordLink1stPage);
    }
    if (ref->d->bSetWordLinkOtherPage == true) {
      free(ref->d->genWordLinkOtherPage);
    }
    if (ref->d->bSetWordLinkSeparator == true) {
      free(ref->d->genWordLinkSeparator);
    }
  }
  return true;
}

/** For rcData list:
Create a new <code>rcData</code> with this key char and this wordPages.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param ref pass in the reference char struct.
@param aChar the char key.
@param aWordPages pass in the wordPages.
@return created rcData or <code>NULL</code> if failure.
*/
rcData *refChar_createRcData(refChar ref, 
    char aChar, wordPages aWordPages) {
  /* NOT checked
  if (ref == NULL) {return NULL;} */
  rcData *rc;
  FN_MALLOC_ERRMSG(rc, ref->d->sizeof_rcData, 
      "Error: refChar_createRcData(..) malloc(rc)")
  if (rc == NULL) {return NULL;}
  /* for alphabet always use lower case to compare */
  rc->aChar = tolower((unsigned char) aChar);
  /* create an empty sorted list */
  rc->list = ysortedList_create(
      ref->d->wpfn->free, ref->d->wpfn->compareWordPages);
  if (rc->list == NULL) {
    perror("Error: refChar_createRcData(..) rc->list == NULL");
    refChar_freeRcData((void *) &rc);
    return NULL;
  }
  rc->list->setFnDoEqualAction(rc->list, refChar_wordListDoEqualAction);
  rc->list->setFnDoAddPrepareAction(rc->list,
      refChar_wordListDoAddPrepareAction);
  /* add this one item. Item allow to be NULL. 
    If item is NULL, the list will have one item that has a NULL content */
  rc->list->addItem(rc->list, (void *) aWordPages, (void *) ref);
  return rc;
}

/** For rcData list:
Free the reference char data struct <code>rcData</code>, 
free up the memory.
<p>Use <code>void</code> to provide customizable function 
for other library, so they can free memory of similar struct 
through commond function call.
i.e. it is NOT declared as 
"<code>void (*free)(rcData **rcDataP);</code>", 
or NOT using type <code>rcData *</code>.
</p>
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param rcDataP pass in the address of <code>rcData *</code> to be free.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_freeRcData(void **rcDataP) {
  /* NOT critical if refChar is NULL, assume all's fine */
  if (rcDataP == NULL) {return true;}
  /* free the linked list (word list) */
  if (((rcData *) *rcDataP)->list != NULL
      && ((rcData *) *rcDataP)->list->free(&((rcData *) *rcDataP)->list) == false) {
    /* possible memory leak */
    perror("Error: refChar_freeRcData(..) ..->list->free(&((rcData *) *rcDataP)->list) == false");
    return false;
  }
  *rcDataP = NULL;
  return true;
}

/** For rcData list:
Compare the ASCII char in these rcData. (Case sensitive)
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param aRcData1 pass in the rcData as <code>void *</code>.
@param aRcData2 pass in the rcData as <code>void *</code>.
@return Returns <code>g_replyEqual</code> if 
  <code>aRcData1</code> and <code>aRcData2</code> are equal. 
  Returns <code>g_replyLesser</code> or <code>g_replyGreater</code>
  if <code>aRcData1</code> is less than or 
  greater than <code>aRcData2</code> respectively.
  Otherwise <code>g_replyFail</code> for failure.
*/
g_reply refChar_compareRcDatas(const void *aRcData1, 
    const void *aRcData2) {
  /* NOT checked
  if (aRcData1 == NULL || aRcData1->aChar == NULL 
      || aRcData2 == NULL || aRcData2->aChar == NULL) {
    return g_replyFail;
  } */
  if (((rcData *) aRcData1)->aChar < ((rcData *) aRcData2)->aChar) {
    return g_replyLesser;
  } else if (((rcData *) aRcData1)->aChar > ((rcData *) aRcData2)->aChar) {
    return g_replyGreater;
  }
  return g_replyEqual;
}

/** For rcData list:
Add a word & its page into reference character list.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param ref pass in the reference char struct.
@param word pass in the word.
@param wordLen the length of the <code>word</code>.
@param aPage the page number to add.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_rcListAdd(refChar ref, const char *word, 
    const unsigned int wordLen, const char *aPage) {
  /* NOT checked
  if (ref == NULL || ref->d == NULL 
      || word == NULL || wordLen <= 0 || aPage == NULL) {
    return false;
  } */
  /* prepare temp rcData - ready for comparison 
    for alphabet always use lower case to compare */
  ref->d->cmpRcData->aChar = tolower((unsigned char) word[0]);
  /* prepare temp wordPages - ready for comparison  */
  if (ref->d->wpfn->setWordPagesWith(ref->d->wpfn, 
      ref->d->cmpWordPages, word, wordLen, aPage) == false) {
    perror("Error: refChar_rcListAdd(..) ..->wpfn->setWordPagesWith(..) == false");
    return false;
  }
  /* put this wordPages in, do NOT add */
  ref->d->cmpRcData->list->setItemNOTFree(ref->d->cmpRcData->list, 
      (void *) ref->d->cmpWordPages);
  /* add temp rcData into rcList */
  ref->d->rcList->addItem(ref->d->rcList, 
      (void *) ref->d->cmpRcData,
      (void *) ref);
  return true;
}

/** For rcData list:
A customized function for <code>ysortedList addItem(..)</code>.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param rcList the sorted list.
@param anItem the item to be added. 
  (found existing & exact key, 
  add the new item's properties into current item ONLY, 
  will NOT free any object's memory)
@param aControlObj an object that customized function may work with.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_rcListDoEqualAction(sortedList rcList, 
    void *anItem, void *aControlObj) {
  /* This is an existing reference char,
    move properties (wordPages) from current item 
    into current rcData in the list 
  1st list is rcList
  2nd list is the word list */
  ((rcData *) rcList->getItem(rcList))->list->addItem(
      ((rcData *) rcList->getItem(rcList))->list, 
      (wordPages) ((rcData *) anItem)->list->getItem(
          ((rcData *) anItem)->list),
      aControlObj);
  /* clear "anItem", do NOT allow any reference to the moved item,
    so it can NOT free it (the moved item) 
    NB: this "anItem" is actually our ref->d->cmpRcData */
  ((rcData *) anItem)->list->setItemNOTFree(((rcData *) anItem)->list, NULL);
  /* Will keep on using ref->d->cmpRcData 
    as a container for new item and or comparison use 
    do NOT free the this object. */
  return true;
}

/** For rcData list:
A customized function for <code>ysortedList addItem(..)</code>.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param aControlObj an object that customized function may work with.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_rcListDoAddPrepareAction(void *aControlObj) {
  /* This is a new reference char, 
    ref->d->cmpRcData is carrying its data.
  . create a new rcData container for our ref->d->cmpRcData. 
  . ref->d->cmpRcData previous content (the new reference char) 
    will be added into rcList by the sortedList, 
    that data should NOT be access directly. */
  ((refChar) aControlObj)->d->cmpRcData = refChar_createRcData(
      (refChar) aControlObj, '\0', NULL);
  if (((refChar) aControlObj)->d->cmpRcData == NULL) {
    perror("Error: refChar_rcListDoAddPrepareAction(..) ..->d->cmpRcData == NULL");
    return false;
  }
  return true;
}

/** For word list.
A customized function for <code>ysortedList addItem(..)</code>.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param list the sorted list.
@param anItem the item to be added. 
  (found existing & exact word, add this page ONLY, 
  will free memory of anItem object)
@param aControlObj an object that customized function may work with.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_wordListDoEqualAction(sortedList list, 
    void *anItem, void *aControlObj) {
  /* found existing & exact word, add this page ONLY
  NOT using any variable,.. save memory & speed?
  i.e.
  wpState wps = ref->d->wpfn->addPage(ref->d->wpfn, wp, pageNum); */
  wpState wps = ((refChar) aControlObj)->d->wpfn->addPage(
      ((refChar) aControlObj)->d->wpfn,
      (wordPages) list->getItem(list), 
      ((refChar) aControlObj)->d->wpfn->get1stPage(
          (wordPages) anItem));
  /* do NOT free:
  . "anItem" object which is ref->d->cmpWordPages it will be reused 
  . the page "aPage", it's NOT a cloned copy, 
    ref->d->cmpWordPages will handle when to free it */
  if (wps == wpSuccess) {
    /* new "word & page" relation */
    (((refChar) aControlObj)->d->cntTotalRelations)++;
  } else if (wps == wpFail) {
    /* skip a page */
    perror("Error: refChar_wordListDoEqualAction(..) ..->d->wpfn->addPage(..) == wpFail");
    return false;
  } /* wpIgnored */
  return true;
}

/** For word list.
A customized function for <code>ysortedList addItem(..)</code>.
Assumtion: previous code check all pass in parameter to be valid!
This function is for internal use ONLY, do NOT expose this.
@param aControlObj an object that customized function may work with.
@return <code>true</code> if successfully otherwise <code>false</code>.
*/
boolean refChar_wordListDoAddPrepareAction(void *aControlObj) {
  /* This is a new word, 
    ref->d->cmpWordPages is carrying its data.
  . create a new wordPages container for our ref->d->cmpWordPages. 
  . ref->d->cmpWordPages previous content (the new word & its page) 
    will be added into the word list by the sortedList, 
    that data should NOT be access directly. */
  ((refChar) aControlObj)->d->cmpWordPages = 
      ((refChar) aControlObj)->d->wpfn->createWith(
          ((refChar) aControlObj)->d->wpfn, NULL, 0, NULL);
  if (((refChar) aControlObj)->d->cmpWordPages == NULL) {
    perror("Error: refChar_wordListDoAddPrepareAction(..) ..->d->cmpWordPages == NULL");
    return false;
  }
  /* new "word" */
  (((refChar) aControlObj)->d->cntTotalWords)++;
  /* new "word & page" relation */
  (((refChar) aControlObj)->d->cntTotalRelations)++;
  return true;
}
